package TwoClientGameConnection;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.LinkedList;

import org.jdom.JDOMException;

/**
 * In order for a server to communicate with a client, it must
 * create a new thread. This class creates is runnable so upon the
 * acceptance of a new client a thread is created and this class
 * facilitates the read/write capabilities of the server to the client.
 * @author Jace Ferguson
 * @filename TCGServerThread.java
 */
public class TCGServerThread implements Runnable {
	private static Integer threadCount = 0;
	/**
	 * Decrement the class thread count.
	 */
	public static void decrementThreadCount()
	{
		--TCGServerThread.threadCount;
	}
	/**
	 * Get the class thread count.
	 * @return
	 */
	public static int getThreadCount()
	{
		return TCGServerThread.threadCount;
	}
	/**
	 * Increment the class thread count.
	 */
	public static void incrementThreadCount()
	{
		++TCGServerThread.threadCount;
	}
	private Socket socket = null;
	private PrintWriter os;
	private BufferedReader is;
	private TCGServer server;
	private String hostAlias;
	private String host;
	private TCGMessage lastMessage;

	private Integer attemptedSends = 0;

	private LinkedList<TCGMessage> outGoingMessages;

	private int status;

	/**
	 * Construct the thread and connect the socket and io stuff.
	 * @param socket	Socket accepted by the game server.
	 * @param server	TCGServer to send messages to.
	 */
	public TCGServerThread(Socket socket, TCGServer server)
	{
		this.server = server;
		this.socket = socket;
		this.outGoingMessages = new LinkedList<TCGMessage>();
		this.host = TCGGeneralLib.getHostIPAddress(socket.getInetAddress());
		try{
			this.os = new PrintWriter(new OutputStreamWriter(this.socket.getOutputStream()));
			this.is = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
			TCGServerThread.incrementThreadCount();
			this.hostAlias = "Thread" + TCGServerThread.getThreadCount();
		}catch(IOException e)
		{
			this.server.sendErrorToProtocol(new TCGGeneralException("Unable to create server thread for connection. Reason: " + e));
		}
	}

	/**
	 * On each iteration check for data waiting to be read.
	 * If there is data, handle the data.
	 */
	public void doIteration()
	{		
		String messageRecieved = "";
		String line;
		try{
			while((line = this.is.readLine()) != null)
			{
				messageRecieved += line;
				//Without this, readLine blocks and the message gets stopped. 
				//This waits until the end tag of the XML has been found then passes the 
				//message on to be processed.
				if(messageRecieved.substring(messageRecieved.length() - 13, messageRecieved.length()).equals("</TCGMessage>"))
				{
					break;
				}
			}
			//create a message object from the transmitted message.
			TCGMessage message = TCGMessageReader.ParseMessage(messageRecieved);
			//Send a reply that the message was received correctly.
			this.replyMessageValid(message);
			//Send the message to the switchboard for processing.
			this.switchBoard(message);
		}
		catch(IOException e) //The message couldn't be read. Request copy
		{
			this.requestLastMessageCopy();
		}
		catch(JDOMException e) //The message didn't parse, maybe garbled. Request copy.
		{
			this.requestLastMessageCopy();
		}
	}

	/**
	 * Return the clients host string.
	 * @return	String
	 */
	public String getHost()
	{
		return this.host;
	}

	/**
	 * Return the host's alias name.
	 * @return	String alias.
	 */
	public String getHostAlias()
	{
		return this.hostAlias;
	}

	/**
	 * Get the server status
	 * @return	int
	 */
	public int getStatus()
	{
		return this.status;
	}

	/**
	 * Attempt to close the thread and remove it from 
	 * the server so it could accept another.
	 */
	public void killThread()
	{
		try
		{
			this.os.close();
			this.is.close();
			this.socket.close();
			this.server.removeThread(this);
		}catch(Exception e)
		{
			this.server.sendErrorToProtocol(new TCGGeneralException("Error shutting down thread. May not be able to accept new clients. Reason: " + e));
		}
	}

	/**
	 * Create a message to send stating that a message was received successfully.
	 * @param message	TCGMessage object	Message that was received.
	 */
	public void replyMessageValid(TCGMessage message)
	{
		/*
		 * However, don't send a reply message to a 
		 * message replying to a successful transmission. 
		 * Leads to an infinite loop of messages and 
		 * lots of bandwidth.
		 */	
		if(!message.getCommand().equals("RMESSAGEVALID"))
		{
			TCGMessage replyMessage = new TCGMessage(this.server.getMessageId());
			replyMessage.setMessageType(TCGMessage.MESSAGE_TYPE_HIGH);
			replyMessage.setCommand("RMESSAGEVALID");
			this.writeMessage(replyMessage);
		}
	}

	/**
	 * Create a message to send to the server to request a copy of the
	 * last message that was transmitted.
	 */
	public void requestLastMessageCopy()
	{
		TCGMessage request = new TCGMessage(this.server.getMessageId());
		request.setMessageType(TCGMessage.MESSAGE_TYPE_HIGH);
		request.setCommand("REQUESTLAST");
		this.writeMessage(request);
	}

	/**
	 * Run the thread and send a message that a connection was made.
	 * This method is called automatically when the thread is created.
	 * It continues to run iterations until the thread is destroyed.
	 */
	public void run()
	{
		this.sendGreeting();
		while(true)
		{
			this.doIteration();
		}
	}

	/**
	 * Run the send queue to deliver outgoing messages.
	 */
	public void runQueueSend()
	{
		if(!this.outGoingMessages.isEmpty())
		{
			this.writeMessage(this.outGoingMessages.remove());
		}
	}

	/**
	 * Send a greeting to the client upon connection success.
	 */
	public void sendGreeting()
	{
		TCGMessage connectGreeting = new TCGMessage(this.server.getMessageId());
		connectGreeting.setCommand("CONNECTED");
		connectGreeting.addParameter("serverWelcome", "Welcome to the Stratelego server");
		this.sendMessage(connectGreeting);
	}

	/**
	 * Most messages should be sent by sending it to this method.
	 * This method then adds the message to the outgoing message queue and
	 * runs the queue.
	 * @param message	TCGMessage object	message to be sent.
	 */
	public void sendMessage(TCGMessage message)
	{
		this.outGoingMessages.add(message);
		this.runQueueSend();
	}

	/**
	 * Change the host alias. Used for chat messages mainly.
	 * @param alias	String
	 */
	public void setHostAlias(String alias)
	{
		if(alias != null)
			this.hostAlias = alias;
	}

	/**
	 * Set the server status.
	 * @param status	int
	 */
	public void setStatus(int status)
	{
		this.status = status;
	}

	/**
	 * The command processor of the client. It takes the message and determines what
	 * to do depending on the command.
	 * @command	QUIT		The client has requested to quit.
	 * @command RMESSAGEVALID	A message that was sent to the server was received correctly.
	 * @command REQUESTLAST		The message is requesting that the last message be transmitted again.
	 * @command TXWARNING	The server has send a transmission warning. Alert the protocol.
	 * @command CHANGEALIAS	Change the clients host alias.
	 * @command "Default"	Send the message to the protocol for handling.
	 * @param message	TCGMessage object	Message data retrieved from the server.
	 */
	public void switchBoard(TCGMessage message)
	{
		if(message.getCommand().equals("QUIT"))
		{
			this.killThread();
		}
		else if(message.getCommand().equals("RMESSAGEVALID"))
		{
			this.lastMessage = null;
			this.attemptedSends = 0;
			this.runQueueSend();
		}
		else if(message.getCommand().equals("REQUESTLAST"))
		{
			if(this.attemptedSends == 6) //After six attempts it is time to give up.
			{
				TCGMessage warning = new TCGMessage(this.server.getMessageId());
				warning.setCommand("TXWARNING");
				warning.addParameter("warningMessage", "The client has failed to receive a package. It was dropped. Crazy stuff may happen.");
				warning.setMessageType(TCGMessage.MESSAGE_TYPE_ERROR);
				this.writeMessage(warning);
				this.lastMessage = null;
				this.attemptedSends = 0;
				this.runQueueSend();
			}
			else //Try to submit a copy of the message.
			{
				this.lastMessage.setMessageType(TCGMessage.MESSAGE_TYPE_HIGH);
				this.writeMessage(this.lastMessage);	 
			}			 
		}
		else if(message.getCommand().equals("TXWARNING"))
		{
			/*
			 *	Relay all TX warnings back to the client.
			 *	The server doesn't display TX errors.
			 *	A new message id must be requested for the server
			 *	before we send it back to the client.
			 */
			message.setMessageId(this.server.getMessageId());
			this.writeMessage(message);
		}
		else if(message.getCommand().equals("CHANGEALIAS"))
		{
			this.hostAlias = message.getParameterValue("alias");
			this.server.sendMessageToProtocol(message, this.host);
		}
		else
		{
			this.server.sendMessageToProtocol(message, this.host);
		}

	}

	/**
	 * Sends a message to a client. This should only be used to send immediate messages.
	 * Most messages should be send using the sendMessage method so as to 
	 * check that all messages are sent and received properly.
	 * @param message	TCGMessage
	 */
	private void writeMessage(TCGMessage message)
	{
		String textMessage = TCGMessage.convertMessageToString(message);

		try{
			this.os.write(textMessage);
			this.os.flush();
			//Add the message to the queue and wait for reply
			if(message.getMessageType() != TCGMessage.MESSAGE_TYPE_ERROR || message.getMessageType() != TCGMessage.MESSAGE_TYPE_LOW)
			{
				this.lastMessage = message;
				this.attemptedSends++;
			}
		}catch (Exception e)
		{
			this.server.sendErrorToProtocol(new TCGGeneralException("An error occured while transmitting message with id: " + message.getMessageId()));
		} 
	}
}


